/*
Copyright 2011 Raymond Giorgi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

package edu.pitt.cs3530.configuration;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import edu.pitt.cs3530.configuration.FrontEndConfigrationHandler.ControllerConfiguration;
import edu.pitt.cs3530.configuration.FrontEndConfigrationHandler.PredictorConfiguration;
import edu.pitt.cs3530.configuration.FrontEndConfigrationHandler.QosConfiguration;
import edu.pitt.cs3530.configuration.FrontEndConfigrationHandler.ReactorConfiguration;
import edu.pitt.cs3530.controllers.Controller;
import edu.pitt.cs3530.controllers.PredictorController;
import edu.pitt.cs3530.controllers.ReactorController;
import edu.pitt.cs3530.predictors.PredictorThread;
import edu.pitt.cs3530.qos.QoSCalculator;
import edu.pitt.cs3530.reactors.ReactorThread;

/**
 * This class acts as an entry point for all predictors, reactors and
 * controllers. This is done to enable a plugin environment and ensure that all
 * required classes can interact with each other easily
 */
public class QoSCalculatorFacade {
	private static class NamedQos {
		private QoSCalculator qos;
		boolean isDefault;
	}

	private static class NamedPredictor {
		private PredictorThread predictor;
		boolean isDefault;
	}

	private static class NamedReactor {
		private ReactorThread reactor;
		boolean isDefault;
	}

	private static class NamedController {
		private Controller controller;
		private List<String> listenTos = new LinkedList<String>();
		boolean isDefault;
	}

	private static Map<String, NamedController> controllers = new HashMap<String, NamedController>();
	private static Map<String, NamedQos> qoses = new HashMap<String, NamedQos>();
	private static Map<String, NamedPredictor> predictors = new HashMap<String, NamedPredictor>();
	private static Map<String, NamedReactor> reactors = new HashMap<String, NamedReactor>();

	private QoSCalculatorFacade() {
		controllers = new HashMap<String, NamedController>();
		qoses = new HashMap<String, NamedQos>();
		predictors = new HashMap<String, NamedPredictor>();
		reactors = new HashMap<String, NamedReactor>();
	}

	private static QoSCalculatorFacade _instance;

	public static QoSCalculatorFacade getInstance() {
		if (_instance == null) {
			_instance = new QoSCalculatorFacade();
		}
		return _instance;
	}

	/**
	 * Register a class that determines the current Quality of Service
	 * 
	 * @param conf
	 *            The configuration of the class (from the XML File)
	 */
	public static void addQos(QosConfiguration conf) {
		try {
			Class cls = Class.forName(conf.getClassName());
			Constructor constructor = cls.getConstructor(Map.class);
			Object newObj = constructor.newInstance(conf.getParameterMap());
			if (newObj instanceof QoSCalculator) {
				QoSCalculator qos = (QoSCalculator) newObj;
				NamedQos namedQos = new NamedQos();
				namedQos.qos = qos;
				namedQos.isDefault = conf.isDefault();
				qoses.put(conf.getShortName(), namedQos);
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Get the named quality of service calculator
	 * 
	 * @param name
	 *            The name of the quality of service calculator to be returned
	 * @return The named quality of service calculator
	 */
	public static QoSCalculator getQos(String name) {
		return qoses.get(name).qos;
	}

	/**
	 * Get the default quality of service calculator designated by the 'default'
	 * XML option
	 * 
	 * @return The default quality of service calculator. If no default is
	 *         named, this returns the first qos calculator
	 */
	public static QoSCalculator getDefaultQos() {
		QoSCalculator retval = null;
		for (Entry<String, NamedQos> entry : qoses.entrySet()) {
			if (retval == null) {
				retval = entry.getValue().qos;
			} else if (entry.getValue().isDefault) {
				retval = entry.getValue().qos;
			}
		}
		return retval;
	}

	/**
	 * Register a class that determines predicts the quality of service
	 * 
	 * @param conf
	 *            The configuration of the class (from the XML File)
	 */
	public static void addPredictor(PredictorConfiguration conf) {
		try {
			Class cls = Class.forName(conf.getClassName());
			Long windowTime = conf.getWindowTime();
			Long predictionTime = conf.getPredictionTime();
			Integer intervals = conf.getNumberOfIntervals();
			Long sleepTime = conf.getSleepTime();
			String qosName = conf.getQosName();
			Map<String, String> parameterMap = conf.getParameterMap();
			Constructor constructor = cls.getConstructor(windowTime.TYPE,
					predictionTime.TYPE, intervals.TYPE, sleepTime.TYPE,
					qosName.getClass(), Map.class);
			Object predictor = constructor
					.newInstance(windowTime, predictionTime, intervals,
							sleepTime, qosName, parameterMap);
			if (predictor instanceof PredictorThread) {
				NamedPredictor np = new NamedPredictor();
				np.isDefault = conf.isDefault();
				np.predictor = (PredictorThread) predictor;
				predictors.put(conf.getShortName(), np);
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Get the named predictor thread
	 * 
	 * @param name
	 *            The name of the predictor thread to be returned
	 * @return The named predictor thread or 'null' if none has been named
	 */
	public static PredictorThread getPredictor(String name) {
		return predictors.get(name).predictor;
	}

	/**
	 * Get the predictor marked with the 'default' attribute in the XML
	 * 
	 * @return The default predictor first, the first found predictor next, or
	 *         null if no predictors have been registered
	 */
	public static PredictorThread getDefaultPredictor() {
		PredictorThread retval = null;
		for (Entry<String, NamedPredictor> entry : predictors.entrySet()) {
			if (retval == null) {
				retval = entry.getValue().predictor;
			} else if (entry.getValue().isDefault) {
				retval = entry.getValue().predictor;
			}
		}
		return retval;
	}

	/**
	 * 
	 * @param conf
	 */
	public static void addReactor(ReactorConfiguration conf) {
		try {
			Class cls = Class.forName(conf.getClassName());
			Long deadline = conf.getDeadline();
			Long early = conf.getEarly();
			Long windowTime = conf.getWindowTime();
			Long sleepTime = conf.getSleepTime();
			String qosName = conf.getQosName();
			Map<String, String> parameterMap = conf.getParameterMap();
			Constructor constructor = cls.getConstructor(deadline.TYPE,
					early.TYPE, windowTime.TYPE, sleepTime.TYPE, qosName
							.getClass(), Map.class);
			Object obj = constructor.newInstance(deadline, early, windowTime,
					sleepTime, qosName, parameterMap);
			if (obj instanceof ReactorThread) {
				NamedReactor nr = new NamedReactor();
				nr.reactor = (ReactorThread) obj;
				nr.isDefault = conf.isDefault();
				reactors.put(conf.getShortName(), nr);
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public static ReactorThread getReactor(String name) {
		return reactors.get(name).reactor;
	}

	public static ReactorThread getDefaultReactor() {
		ReactorThread retval = null;
		for (Entry<String, NamedReactor> entry : reactors.entrySet()) {
			if (retval == null) {
				retval = entry.getValue().reactor;
			} else if (entry.getValue().isDefault) {
				retval = entry.getValue().reactor;
			}
		}
		return retval;
	}

	public static void addController(ControllerConfiguration conf) {
		try {
			Class cls = Class.forName(conf.getClassName());
			Double upperThreshold = conf.getUpperThreshold();
			Double lowerThreshold = conf.getLowerThreshold();
			Long timeToScaleUp = conf.getTimeToScaleUp();
			Long timeToScaleDown = conf.getTimeToScaleDown();
			Long timeToTurnOn = conf.getTimeToTurnOn();
			Long timeToTurnOff = conf.getTimeToTurnOff();
			Map<String, String> parameterMap = conf.getParameterMap();
			Constructor constructor = cls.getConstructor(upperThreshold.TYPE,
					lowerThreshold.TYPE, timeToScaleUp.TYPE,
					timeToScaleDown.TYPE, timeToTurnOn.TYPE,
					timeToTurnOff.TYPE, Map.class);
			Object obj = constructor.newInstance(upperThreshold,
					lowerThreshold, timeToScaleUp, timeToScaleDown,
					timeToTurnOn, timeToTurnOff, parameterMap);
			if (obj instanceof Controller) {
				NamedController nc = new NamedController();
				nc.controller = (Controller) obj;
				nc.isDefault = conf.isDefault();
				controllers.put(conf.getShortName(), nc);
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static Controller getController(String name) {
		return controllers.get(name).controller;
	}

	public static PredictorController getDefaultPredictorController() {
		PredictorController retval = null;
		for (Entry<String, NamedController> entry : controllers.entrySet()) {
			if ((retval == null)
					&& (entry.getValue().controller instanceof PredictorController)) {
				retval = (PredictorController) entry.getValue().controller;
			} else if ((entry.getValue().isDefault)
					&& (entry.getValue().controller instanceof PredictorController)) {
				retval = (PredictorController) entry.getValue().controller;
			}
		}
		return retval;
	}

	public static ReactorController getDefaultReactorController() {
		ReactorController retval = null;
		for (Entry<String, NamedController> entry : controllers.entrySet()) {
			if ((retval == null)
					&& (entry.getValue().controller instanceof ReactorController)) {
				retval = (ReactorController) entry.getValue().controller;
			} else if ((entry.getValue().isDefault)
					&& (entry.getValue().controller instanceof ReactorController)) {
				retval = (ReactorController) entry.getValue().controller;
			}
		}
		return retval;
	}

	/**
	 * This thread starts the predictors and reactors and registers all
	 * controllers with their associated predictors and reactors
	 */
	public static void startAll() {
		for (Entry<String, NamedReactor> entry : reactors.entrySet()) {
			entry.getValue().reactor.start();
		}
		for (Entry<String, NamedPredictor> entry : predictors.entrySet()) {
			entry.getValue().predictor.start();
		}
		for (Entry<String, NamedController> entry : controllers.entrySet()) {
			Controller controller = entry.getValue().controller;
			if (!entry.getValue().listenTos.isEmpty()) {
				for (String threadName : entry.getValue().listenTos) {
					registerController(controller, threadName);
				}
			} else {
				registerController(controller);
			}
		}
	}

	/**
	 * This will not work!!!!!
	 * 
	 * @param name
	 */
	public static void startThread(String name) {
		Thread thread = predictors.get(name).predictor;
		if (thread == null) {
			thread = reactors.get(name).reactor;
		}
		if (thread != null) {
			thread.start();
		}
	}

	/**
	 * This will work, but a thread will never be able to be woken up / created
	 * again
	 * 
	 * @param name
	 */
	public static void stopThread(String name) {
		Thread thread = predictors.get(name).predictor;
		if (thread == null) {
			thread = reactors.get(name).reactor;
		}
		if (thread != null) {
			thread.stop();
		}
	}

	/**
	 * This class registers a specific controller with a thread after
	 * initialization time. If the controller doesn't extend from
	 * PredictorController or ReactorController, nothing happens
	 * 
	 * @param controller
	 *            The controller to be registered
	 * @param name
	 *            The name of the thread to register the controller with
	 */
	public static void registerController(Controller controller, String name) {
		if (controller instanceof PredictorController) {
			getPredictor(name).addListener((PredictorController) controller);
		} else if (controller instanceof ReactorController) {
			getReactor(name).addListener((ReactorController) controller);
		}
	}

	/**
	 * This method registers a specific controller with the default predictor /
	 * reactor thread during the threads execution
	 * 
	 * @param controller
	 *            The controller to be added
	 */
	public static void registerController(Controller controller) {
		if (controller instanceof PredictorController) {
			getDefaultPredictor().addListener((PredictorController) controller);
		} else if (controller instanceof ReactorController) {
			getDefaultReactor().addListener((ReactorController) controller);
		}
	}

	/**
	 * This class deregisters a specific controller with a thread after
	 * initialization time. If the controller doesn't extend from
	 * PredictorController or ReactorController, nothing happens
	 * 
	 * @param controller
	 *            The controller to be registered
	 * @param name
	 *            The name of the thread to register the controller with
	 */
	public static void deregisterController(Controller controller, String name) {
		if (controller instanceof PredictorController) {
			getPredictor(name).removeListener((PredictorController) controller);
		} else if (controller instanceof ReactorController) {
			getReactor(name).removeListener((ReactorController) controller);
		}
	}

	/**
	 * This method deregisters a specific controller with the default predictor
	 * / reactor thread during the threads execution
	 * 
	 * @param controller
	 *            The controller to be added
	 */
	public static void deregisterController(Controller controller) {
		if (controller instanceof PredictorController) {
			getDefaultPredictor().removeListener(
					(PredictorController) controller);
		} else if (controller instanceof ReactorController) {
			getDefaultReactor().removeListener((ReactorController) controller);
		}
	}
}
